'''
Converts parsed data from Parser.decodeReplay into feature vectors

Rather poor organization due to initial unfamiliarity with python, and lack of
need for efficiency realtive to bot and learning

Authors:
    Shengling Hu

Usage:
    From containing directory
    python parser.py

    or

    python parser.py -g 111280
'''
import csv
import pickle
from pprint import pprint
import os
from math import sqrt

REPLAY_PREFIX = os.path.join('..', '..', 'data', 'replays')
FEATURE_PREFIX = os.path.join('..', '..', 'data', 'features')
if (not os.path.isdir(REPLAY_PREFIX)):
    os.mkdir(REPLAY_PREFIX)
if (not os.path.isdir(FEATURE_PREFIX)):
    os.mkdir(FEATURE_PREFIX)
DEFAULT_GAME_NO = '115671'
DEFAULT_REPLAY = os.path.join(REPLAY_PREFIX, DEFAULT_GAME_NO)
DEFAULT_FEATURE = os.path.join(FEATURE_PREFIX, DEFAULT_GAME_NO)
GAME_LIST_PATH = os.path.join('..', '..', 'data', 'games.txt')
MAXROW = 128
MAXCOL = 112
MAXTURNS = 1001
VISION_RADIUS_SQ = 77
TURNS_PER = 20

#for precomputed vision to be used in feature computation
vision = []
hives = []

def extractFeature(replay_file,functions):
    """
        Extracts list of features( where function maps replay_file to
            a feature vector, which may be part of a larger feature vector
        functions is a list of function that takes a player's data and returns
            a list of computed features in each region, with f[0] computing the
            target fution:
                [f_in_r1, f_in_r2,...]
            Currently input is : ([winner],hills,states[i][turn])
            winner is index of winner
            states[i][turn]
            hills is list of hill locations, states

        fun_fea_num[i] is the number of features associated with functions[i]
        replay_file is the file containing the pickled player data

        Outputs a list for the winner the CURRENT format of:
            list of features vectors each turn segment
            in the following format
                [f[0](*data*),f[1](*data*)...]

    """
    #tupled data
    a = pickle.load(replay_file)
    #tupled game data, where data is small peices in the form of a list
    (data,hills,states) = a
    features = []
    #data[0] is winner
    i = data[0]
    l = len(states[i])
    #Unhard code this
    num_regions = 100
    global vision
    global hives

    hives = get_hives_features(data,hills)

    for set,turns in enumerate(range(0,l,TURNS_PER)):
        #precompute vision 5 turns at a time
        vision = []
        for j in range(0,min(l-turns,TURNS_PER)):
            turn = turns + j
            vision.append(get_vision_matix(states[i][turn],VISION_RADIUS_SQ))
        turn_features = [[] for dummy in range(num_regions)]
        #Last feature number kept for labeling purposes
        for fnum,f in enumerate(functions):
            v = []
            #Creates list of features from f over TURNS_PER turns
            for j in range(0,min(l-turns,TURNS_PER)):
                turn = turns + j
                v.append(f(data,hills,[states[n][turn] for n in range(len(states))],turn))

            #Averages them and put them in features
            for pos in range(0,num_regions):
                vals = [v[n][pos] for n in range(len(v))]
                turn_features[pos].append(float(sum(vals))/len(vals))
        features.append(turn_features)

    return features

"""
    Subfunctions to be used to compute features
"""
def getHives(data,hills,ants,turn):
    return hives

def get_hives_features(data,hills):
    winner = data[0]
    results = [0 for i in range(100)]
    hill = hills[winner]
    for other_hill in hills:
	if not (hill == other_hill):
            results[REGION_MAP(hill,other_hill)] += 1.0

    return results

def getDistance(data,hills,ants,turn):
    return DISTANCES

def getTurnSet(data,hills,ants,turn):
    results = [turn//TURNS_PER for i in range(100)]
    return results

def getFractionAnts(data,hills,ants,turn):
    winner = data[0]
    results = [0 for i in range(100)]
    total = len(ants[winner])
    hill = hills[winner]
    for r,c in ants[winner]:
        results[REGION_MAP(hill,(r,c))] += 1.0/total
    return results

def getTotalAnts(data,hills,ants,turn):
    winner = data[0]
    results = [len(ants[winner]) for i in range(100)]
    return results

def getVisionAmount(data,hills,ants,turn):
    winner = data[0]
    results = [0 for i in range(100)]
    hill = hills[winner]
    for r,row_vision in enumerate(vision[turn%TURNS_PER]):
        for c,sq_vision in enumerate(row_vision):
            if sq_vision:
                results[REGION_MAP(hill,(r,c))] += 1
    return results

def getEnemiesVisible(data,hills,ants,turn):
    winner = data[0]
    results = [0 for i in range(100)]
    hill = hills[winner]
    for i, player_ants in enumerate(ants):
        if not (i == winner):
            for r,c in player_ants:
                if vision[turn%TURNS_PER][r%MAXROW][c%MAXCOL]:
                    results[REGION_MAP(hill,(r,c))] += 1
    return results

def mapRadially(hill,location):
    '''
    Returns i if less than radii[i]... otherwise
    Not used anymore

    '''
    maxDis = (MAXROW**2 + MAXCOL**2)**.5
    #range generation if decide to not hardcode, to be finished
    radii = [5, 10, 30, 50]
    y = abs(hill[0]-location[0])%MAXROW
    x = abs(hill[1]-location[1])%MAXCOL
    y = min(y,MAXROW-y)
    x = min(x,MAXCOL-x)
    d = (x**2 + y**2)**.5
    for n,r in enumerate(radii):
        if d < r:
            return n
    return len(radii)

def computeDistance(RDIM,CDIM,n):
    results = []
    r_loc = -RDIM*.5*(1-1.0/n)
    for r in range(n):
        c_loc = -CDIM*.5*(1-1.0/n)
        for c in range(n):
            results.append((r_loc**2 + c_loc**2)**.5)
            c_loc += float(CDIM)/n
        r_loc += float(RDIM)/n
    return results

DISTANCES = computeDistance(MAXROW,MAXCOL,10)

def mapSquaredly(hill,location):
    """
        Map the locations by rectangles indicated at the top of
        this function

    """
    nR = 10
    nC = 10
    y0 = hill[0]-MAXROW/2
    x0 = hill[1]-MAXCOL/2
    #range generation if decide to not hardcode, to be finished
    y_rel = abs(location[0]-y0)%MAXROW
    x_rel = abs(location[1]-x0)%MAXCOL
    r = y_rel//(float(MAXROW)/nR)
    c = x_rel//(float(MAXCOL)/nC)
    return int(round(r*nC + c))

REGION_MAP = mapSquaredly

def get_neighbors(loc, max_dist_sq):
    """
        ADAPTED FROM ANTS TOOLS FROM AI CHALLENGE
        Return a list of squares within a given distance of loc

        Loc is included in the list
        For all squares returned: 0 < distance(loc,square) <= max_dist

        Offsets are calculated so that:
          -height <= row+offset_row < height (and similarly for col)
          negative indicies on self.map wrap thanks to python
    """
    offsets = []
    mx = int(sqrt(max_dist_sq))
    msq = max_dist_sq
    for d_row in range(-mx,mx+1):
        for d_col in range(-mx,mx+1):
            d = d_row**2 + d_col**2
            if 0 <= d <= msq:
                offsets.append((
                    (d_row+loc[0])%MAXROW,
                    (d_col+loc[1])%MAXROW
                ))
    return offsets

def get_vision_matix(locs,radius):

    visible = [[False for i in range(MAXCOL)] for i in range(MAXROW)]
    for ant in locs:
        for (r,c) in get_neighbors(ant,radius):
            visible[r%MAXROW][c%MAXCOL] = True

    return visible

def get_visible(vision, their_ants):
    """
        Returns the list of elements in locs that is visible in vision

    """
    visible = []
    for (r,c) in their_ants:
        if vision[r][c]:
            visible.append((r,c))

    return visible

'''
Writes features to file
'''
def write_features(feature_file,features):
    for (turn,turn_features) in enumerate(features[:len(features)-1]):
        next_turn_features = features[turn+1]
        for (reg,feature_vector) in enumerate(turn_features):
            feature_file.write(str(next_turn_features[reg][0]))
            for i,feature in enumerate(feature_vector[1:]):
                feature_file.write(' ' + str(i+1) + ':' + str(feature))
            feature_file.write('\n')


'''
Extract features for one game replay specified by int gameid.
Current features and parameters are in code comments.
'''
def extractFeatureForReplay(game):
        print('Extracting features for game ' + str(game) + '\n')
        in_file=open(os.path.join(REPLAY_PREFIX,str(game)),'r')
        all_features = extractFeature(in_file,
                                      [getFractionAnts,
                                       getDistance,
                                       getTurnSet,
                                       getVisionAmount,
                                       getEnemiesVisible,
				       getHives,
                                       getTotalAnts])
        in_file.close()
        out_file = open(os.path.join(FEATURE_PREFIX,str(game)),'w')
        write_features(out_file,all_features)
        out_file.close()
        print('Done with ' +  str(game) + '\n')


if __name__ == '__main__':
    #games = [115671] #More , 116944, 117034, 117067, 117297, 149452, 149357, 149342, 149316, 149186, 149166, 147481]
    gameReader = csv.reader(open(GAME_LIST_PATH))
    # one line, one list of gameid strings
    gameStr = []
    for item in gameReader:
        gameStr = item
    #print gameStr
    game_list = [int(item) for item in gameStr]
    count = 0

    for gameid in game_list[0:20]:
        count = count + 1
        #if count > 1:
        #    break
        print count
        extractFeatureForReplay(gameid)
